package com.inepex.inechart.client.cartesian;

import java.util.ArrayList;

import com.inepex.inechart.client.IneChartEventManager;
import com.inepex.inechart.client.axes.AxesGWT;
import com.inepex.inechart.client.displayengine.CartesianRectangleTile;
import com.inepex.inechart.client.displayengine.DisplayEngine;
import com.inepex.inechart.client.displayengine.Layer;
import com.inepex.inechart.client.displayengine.LayerGroup;
import com.inepex.inechart.client.displayengine.ViewportWidget;
import com.inepex.inechart.client.event.DataEntrySelectionEvent;
import com.inepex.inechart.client.event.DataSetChangeEvent;
import com.inepex.inechart.client.event.ViewportChangeEvent;
import com.inepex.inechart.client.jobscheduler.HasScheduledJob;
import com.inepex.inechart.shared.CartesianChartModule;
import com.inepex.inechart.shared.CartesianException;
import com.inepex.inechart.shared.axes.Axis;
import com.inepex.inechart.shared.axes.AxisDirection;
import com.inepex.inechart.shared.util.PaddingUtil;
import com.inepex.inegraphics.impl.client.DrawingAreaGWT;
import com.inepex.inegraphics.shared.GraphicalObjectContainer;

public abstract class CartesianChartModuleGWT extends CartesianChartModule {
	
	protected final CartesianModuleInnerEventHandler innerEventHandler;
	protected final IneChartEventManager ineChartEventManager;
	protected boolean canHandleEvents;
	protected final DisplayEngine displayEngine; 
	protected LayerGroup moduleLayerGroup;
	protected ViewportWidget viewportWidget;
	protected CartesianJobScheduler cartesianJobScheduler;
	protected CartesianRectangleTile actualJobTile;

	protected CartesianChartModuleGWT(AxesGWT axes, IneChartEventManager eventManager, DisplayEngine displayEngine) {
		super(axes);
		innerEventHandler = new CartesianModuleInnerEventHandler(this);
		this.ineChartEventManager = eventManager;
		this.displayEngine = displayEngine;
		moduleLayerGroup = displayEngine.getLayerManager().getDefaultLayerGroup().createLayerGroup();
		viewportWidget = displayEngine.createViewportWidget(this);
		viewportWidget.setTileOwner(innerEventHandler);
		cartesianJobScheduler = new CartesianJobScheduler(this);
		updateCartesianAssist(displayEngine.getChartWidth(), displayEngine.getChartHeight());
	}
	/* cartesian jobs */
	protected void onJobSequenceFinish(){

	}

	protected void clear(HasScheduledJob jobScheduler){
		viewportWidget.clear();
		//TODO

		jobScheduler.onJobFinish();
	}

	protected void prepareAxes(HasScheduledJob jobScheduler){
		alignExtraAxes();
		for (Axis axis : getAllAxesAlignedToThisModule()) {
			boolean xParallel = AxisDirection.isPerpendicular(axis, cartesianAssist.getyAxis());
			axes().axisResolutionChanged(axis);
			axes().createAndCustomizeTicksIf(
					axis,
					xParallel ? actualJobTile.getxFrom() : actualJobTile.getyFrom(),
					xParallel ? actualJobTile.getxTo() : actualJobTile.getyTo());
		}	
		jobScheduler.onJobFinish();
	}

	protected void calcPaddingForAxes(HasScheduledJob jobScheduler) throws CartesianException{
		axes().calcPaddingForAxes(
				getAllAxesAlignedToThisModule(), 
				jobScheduler, 
				CartesianJobScheduler.defaultUpdatePartTreshold,
				actualJobTile.getxFrom(),
				actualJobTile.getxTo(),
				actualJobTile.getyFrom(),
				actualJobTile.getyTo());
	}

	protected void setDimensionsCreateWidgets(HasScheduledJob jobScheduler){
		double[] padding = new double[]{minTopPadding, minRightPadding, minBottomPadding, minTopPadding};
		for (Axis axis : getAllAxesAlignedToThisModule()) {
			padding = PaddingUtil.mergePaddings(padding, axes().getActualModulPaddingForAxis(axis));
		}
		updateCartesianAssist(displayEngine.getChartWidth(), displayEngine.getChartHeight());
		displayEngine.updateViewportWidgetSizeAndPositionInsideChart(this);
		jobScheduler.onJobFinish();
	}

	protected void createGOsForAxes(HasScheduledJob jobScheduler) throws CartesianException{
		axes().createGOsForAxes(getAllAxesAlignedToThisModule(), jobScheduler, CartesianJobScheduler.defaultUpdatePartTreshold, 
				actualJobTile.getxFrom(),
				actualJobTile.getxTo(),
				actualJobTile.getyFrom(),
				actualJobTile.getyTo());
	}

	protected abstract void prepareModel(HasScheduledJob jobScheduler);

	protected abstract void createModelGOs(HasScheduledJob jobScheduler);

	protected abstract void draw(HasScheduledJob jobScheduler);
	
	protected void updateDisplayEngine(HasScheduledJob jobScheduler){
		viewportWidget.setPosition(actualJobTile.getTop(), actualJobTile.getLeft());
		viewportWidget.update();
	}
	
	/* utility methods */
	protected AxesGWT axes(){
		return (AxesGWT) axes;
	}

	protected ArrayList<Axis> getAllAxesAlignedToThisModule(){
		ArrayList<Axis> alignedAxes = new ArrayList<Axis>();
		if(cartesianAssist.getxAxis().getModulToAlign() == this){
			alignedAxes.add(cartesianAssist.getxAxis());
		}
		if(cartesianAssist.getyAxis().getModulToAlign() == this){
			alignedAxes.add(cartesianAssist.getyAxis());
		}
		for (Axis axis : extraAxes) {
			alignedAxes.add(axis);
		}
		return alignedAxes;
	}
	
	/* events */
	protected void onDataSetChange(DataSetChangeEvent event) {}

	protected void onSelect(DataEntrySelectionEvent event) {}

	protected void onDeselect(DataEntrySelectionEvent event) {}

	protected void onMove(ViewportChangeEvent event, double dx, double dy) {}

	protected void onMoveAlongX(ViewportChangeEvent event, double dx) {}

	protected void onMoveAlongY(ViewportChangeEvent event, double dy) {}

	protected void onSet(ViewportChangeEvent event, double xMin, double yMin,
			double xMax, double yMax) {}

	protected void onSetX(ViewportChangeEvent event, double xMin, double xMax) {}

	protected void onSetY(ViewportChangeEvent event, double yMin, double yMax) {}

	protected void beforeShow(CartesianRectangleTile tile){}

	protected void beforeHide(CartesianRectangleTile tile){}

	/* tiling engine related methods */
	/**
	 * Fills up the given model viewport with tile widgets. 
	 * The given interval should enclose (at least in one dimension) the actual model viewport.
	 * Note that at least the actual viewport's tile widget will be created.
	 * @param xMin
	 * @param xMax
	 * @param yMin
	 * @param yMax
	 * @param overWriteExisting
	 */
	protected void createTilesBasedOnActualViewport(double xMin, double xMax, double yMin, double yMax, boolean overWriteExisting){
		double canvasXStart = 0, canvasXEnd = 0, canvasYStart = 0, canvasYEnd = 0;
		switch (cartesianAssist.getxAxis().getAxisDirection()) {
		case Horizontal_Ascending_To_Right:
			canvasXStart = cartesianAssist.modelToCanvasHorizontal(xMin);
			canvasXEnd = cartesianAssist.modelToCanvasHorizontal(xMax);
			break;
		case Horizontal_Ascending_To_Left:
			canvasXEnd = cartesianAssist.modelToCanvasHorizontal(xMin);
			canvasXStart = cartesianAssist.modelToCanvasHorizontal(xMax);
			break;
		case Vertical_Ascending_To_Bottom:
			canvasYStart = cartesianAssist.modelToCanvasVertical(xMin);
			canvasYEnd = cartesianAssist.modelToCanvasVertical(xMax);
			break;
		case Vertical_Ascending_To_Top:
			canvasYEnd = cartesianAssist.modelToCanvasVertical(xMin);
			canvasYStart = cartesianAssist.modelToCanvasVertical(xMax);
			break;
		}
		switch (cartesianAssist.getyAxis().getAxisDirection()) {
		case Horizontal_Ascending_To_Right:
			canvasXStart = cartesianAssist.modelToCanvasHorizontal(yMin);
			canvasXEnd = cartesianAssist.modelToCanvasHorizontal(yMax);
			break;
		case Horizontal_Ascending_To_Left:
			canvasXEnd = cartesianAssist.modelToCanvasHorizontal(yMin);
			canvasXStart = cartesianAssist.modelToCanvasHorizontal(yMax);
			break;
		case Vertical_Ascending_To_Bottom:
			canvasYStart = cartesianAssist.modelToCanvasVertical(yMin);
			canvasYEnd = cartesianAssist.modelToCanvasVertical(yMax);
			break;
		case Vertical_Ascending_To_Top:
			canvasYEnd = cartesianAssist.modelToCanvasVertical(yMin);
			canvasYStart = cartesianAssist.modelToCanvasVertical(yMax);
			break;
		}
		CartesianRectangleTile last;		
		int row = 0, col = 0;
		do{
			do{
				last = getOrCreateTile(row, col, overWriteExisting);
				row++;
			}
			while(last.getBottom() < canvasYEnd);
			last = (CartesianRectangleTile) viewportWidget.getTileTable().getTile(0, col);
			row = -1;
			while(last.getTop() > canvasYStart){
				last = getOrCreateTile(row, col, overWriteExisting);
				row--;
			}
			col++;
		}
		while(last.getRight() < canvasXEnd);
		row = 0;
		col = -1;
		last = (CartesianRectangleTile) viewportWidget.getTileTable().getTile(0, 0);
		while(last.getLeft() > canvasXStart){
			do{
				last = getOrCreateTile(row, col, overWriteExisting);
				row++;
			}
			while(last.getBottom() < canvasYEnd);
			last = (CartesianRectangleTile) viewportWidget.getTileTable().getTile(0, col);
			row = -1;
			while(last.getTop() > canvasYStart){
				last = getOrCreateTile(row, col, overWriteExisting);
				row--;
			}
			col--;
		}
	}

	/**
	 * Creates a {@link CartesianRectangleTile} at the given row and column
	 * @param row
	 * @param col
	 * @return
	 */
	protected CartesianRectangleTile getOrCreateTile(int row, int col, boolean overWriteExisting){
		CartesianRectangleTile tile = (CartesianRectangleTile) viewportWidget.getTileTable().getTile(row, col);
		if(overWriteExisting || tile == null){
			tile = viewportWidget.createCartesianRectangleTile(row, col);
		}			
		double modelTileLeft = cartesianAssist.canvasToModelHorizontal(tile.getLeft());
		double modelTileRight = cartesianAssist.canvasToModelHorizontal(tile.getRight());
		double modelTileTop = cartesianAssist.canvasToModelVertical(tile.getTop());
		double modelTileBot = cartesianAssist.canvasToModelVertical(tile.getBottom());
		switch (cartesianAssist.getxAxis().getAxisDirection()) {
		case Horizontal_Ascending_To_Right:
			tile.setxFrom(modelTileLeft);
			tile.setxTo(modelTileRight);
			break;
		case Horizontal_Ascending_To_Left:
			tile.setxFrom(modelTileRight);
			tile.setxTo(modelTileLeft);
			break;
		case Vertical_Ascending_To_Bottom:
			tile.setxFrom(modelTileTop);
			tile.setxTo(modelTileBot);
			break;
		case Vertical_Ascending_To_Top:
			tile.setxFrom(modelTileBot);
			tile.setxTo(modelTileTop);
			break;
		}
		switch (cartesianAssist.getyAxis().getAxisDirection()) {
		case Horizontal_Ascending_To_Right:
			tile.setyFrom(modelTileLeft);
			tile.setyTo(modelTileRight);
			break;
		case Horizontal_Ascending_To_Left:
			tile.setyFrom(modelTileRight);
			tile.setyTo(modelTileLeft);
			break;
		case Vertical_Ascending_To_Bottom:
			tile.setyFrom(modelTileTop);
			tile.setyTo(modelTileBot);
			break;
		case Vertical_Ascending_To_Top:
			tile.setyFrom(modelTileBot);
			tile.setyTo(modelTileTop);
			break;
		}
		return tile;
	}
	
	protected void setActualJobTileToBase(){
		actualJobTile = getOrCreateTile(0, 0, false);
	}
	
	protected LayerGroup getOrCreateNamedLayerGroup(String name){
		LayerGroup lg = moduleLayerGroup.getNamedLayerGroup(name);
		if(lg == null){
			lg = moduleLayerGroup.createLayerGroup(name);
		}
		return lg;
	}
	
	protected DrawingAreaGWT getOrCreateLayerForActualTile(String layerName, String layerGroupName){
		DrawingAreaGWT canvas = actualJobTile.getDrawingAreaGWTByName(layerName);
		if(canvas == null){ //not present
			canvas = actualJobTile.createDrawingAreaGWT(layerName);
			Layer layer = getOrCreateNamedLayerGroup(layerGroupName).getNamedLayer(layerName);
			if(layer == null){
				layer = getOrCreateNamedLayerGroup(layerGroupName).createLayer(layerName);
			}
			layer.getWidgets().add(canvas.getWidget());
		}
		return canvas;
	}
	
	/* GO alignment (to tiles) */
	protected void alignGraphicalObjectsToActualJobTile(GraphicalObjectContainer goc){
		double dx = -actualJobTile.getLeft();
		double dy = -actualJobTile.getTop();
		goc.moveBasePoints(dx, dy);
	}
	
	protected void alignAllAxesGOToActualJobTile(){
		for (Axis axis : getAllAxesAlignedToThisModule()) {
			alignGraphicalObjectsToActualJobTile(axes().getAxisCalculatorGWTForAxis(axis).getGridLinesAndFills());
			alignGraphicalObjectsToActualJobTile(axes().getAxisCalculatorGWTForAxis(axis).getAxisLineAndTickGOs());
		}
	}
	
}
